/*********************************************************************************
 *      Copyright:  (C) 2019 Wu Yujun<540726307@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  gsmd_tlv.c
 *    Description:  This file gsmd tlv packet
 *                 
 *        Version:  1.0.0(2019年07月16日)
 *         Author:  Wu Yujun <540726307@qq.com>
 *      ChangeLog:  1, Release initial version on "2019年07月16日 15时34分00秒"
 *                 
 ********************************************************************************/
#include "gsmd_tlv.h"


/****************************************************************
 *  描述：登录报文的装包
 *
 *  参数：buf用于返回的登录报文，size为buf的大小，pwd命令是登录的密码字符串
 *
 *  返回值：
 *        失败返回0，成功返回报文大小
 *
 ***********************************************************************/
int packtlv_logon(char *buf, int size, char *pwd)
{
    unsigned short      crc16 = 0;
    int                 pack_len = 0;
    int                 data_len = 0;
    int                 ofset = 0; /*  index position for the buf */

    if(!buf || !pwd || size<TLV_MIN_SIZE )
    {
        printf("Invalid input arguments\n");
        return 0;
    }

    /*  Packet head */
    buf[ofset] = PACK_HEADER;
    ofset += 1;

    /*  Tag */
    buf[ofset] = TAG_LOGON;
    ofset += 1;

    /*  $pwd too long maybe result buffer overflow, so we need check the buffer 
     *           * is large enuf or not. If buf size is not enuf we will truncate $pwd string */
    if( strlen(pwd) <= size-TLV_FIXED_SIZE )
        data_len = strlen(pwd);
    else
        data_len = size-TLV_FIXED_SIZE;

    /*  Length, this packet is passwd length+5Byte () */
    pack_len = data_len + TLV_FIXED_SIZE;

    buf[ofset] = pack_len;
    ofset += 1;

    memcpy(&buf[ofset], pwd, data_len);
    ofset += data_len;

    /*  Calc CRC16 checksum value from Packet Head(buf[0])~ Value(buf[ofset]) */
    crc16 = crc_itu_t(MAGIC_CRC, (unsigned char*)buf, ofset);

    /*  Append the 2 Bytes CRC16 checksum value into the last two bytes in packet buffer */
    ushort_to_bytes((unsigned char*)&buf[ofset], crc16);
    ofset += 2;

    return ofset;
}

/* *****************************
 * 描述：封装PPP拨号上网报文
 * 
 * 参数：
 *      cmd可以是ON,PPP拨号连接，或者是OFF断开PPP拨号连接
 *
 * 返回值：
 *      失败返回0，,成功返回ppp报文大小
 **********************************************************/
int packtlv_ppp(char *buf, int size, int cmd)
{
    unsigned short      crc16;
    int                 pack_len = TLV_FIXED_SIZE+1; /*  Only 1 byte data */

    if(!buf || size<TLV_MIN_SIZE )
    {
        printf("Invalid input arguments\n");
        return 0;
    }

    /*  Packet head */
    buf[0] = PACK_HEADER;

    /*  Tag */
    buf[1] = TAG_PPP;

    /*  Length, this packet total 6 bytes */
    buf[2] = pack_len;

    /*  Value */
    buf[3] = (OFF==cmd) ? 0x00 : 0x01;

    /*  Calc CRC16 checksum value from Packet Head(buf[0])~ Packet Value(buf[3]) */
    crc16 = crc_itu_t(MAGIC_CRC,(unsigned char*)buf, 4);

    /*  Append the 2 Bytes CRC16 checksum value into the last two bytes in packet buffer */
    ushort_to_bytes((unsigned char*)&buf[4], crc16);

    return pack_len;
}


/* ***********************************************************
 * 描述：封装SMS发短信报文
 *
 * 参数：
 *      tel_num是要给哪个号码短信以字符串的形式，mess发短信的内容
 *
 * 返回值：
 *      失败返回0，,成功返回SMS报文大小
 **********************************************************/
int packtlv_sms(char *buf, int size, char *tel_num, char *mess)
{
    unsigned short      crc16;
    int                 pack_len = TLV_FIXED_SIZE+1; /*  Only 1 byte data */
    int                 ofset = 0; /*  index position for the buf */
    int                 num_len ;
    int                 mess_len ;

    if(!buf ||!tel_num || !mess)
    {
        printf("Invalid input arguments\n");
        return 0;
    }

    num_len = strlen(tel_num) ;
    mess_len = strlen(mess) ;

    pack_len = 3+1+num_len+1+mess_len+1+2 ;

    /*  报文总的长度2（帧头和类型）+1(长度位)+1(0x1B)+电话号码长度+1(0x1B)+短信长度strlen(sms_message)+1（0x1C）+2(校验和)  */
    if(size < (pack_len))
    {
        printf("Buf too short!\n") ;
        return 0 ;
    }

    /*  Packet head */
    buf[ofset] = PACK_HEADER;
    ofset = ofset + 1 ;

    /*  Tag */
    buf[ofset] = TAG_SMS;
    ofset = ofset + 1 ;

    /*  Length, this packet total 6 bytes */
    buf[ofset] = pack_len;
    ofset++ ;

    /*  "+"ACSII ,telephone number start */
    buf[ofset] = 0x1B ;
    ofset = ofset + 1 ;

    /*  telephone number */
    if(num_len < 11 || num_len > 16)
    {
        printf("Inbvail telephone number\n") ;
        return 0 ;
    }
    memcpy(&buf[ofset], tel_num, num_len) ;
    ofset = ofset + num_len ;


    /*   "+"ACSII, telephone number end  */
    buf[ofset] = 0x1B ;
    ofset = ofset + 1 ;

    /*   message    */
    memcpy(&buf[ofset], mess, mess_len) ;
    ofset = ofset + mess_len ;

    /*  "-"ACSII, message end */
    buf[ofset] = 0x1C ;
    ofset = ofset +1 ;

    /*  Calc CRC16 checksum value from Packet Head(buf[0])~ Packet Value(buf[ofset]) */
    crc16 = crc_itu_t(MAGIC_CRC,(unsigned char*)buf, pack_len -2 );

    /*  Append the 2 Bytes CRC16 checksum value into the last two bytes in packet buffer */
    ushort_to_bytes((unsigned char*)&buf[ofset], crc16);

    return pack_len;
}


int packtlv_signal(char *buf, int size, int signal)
{
    unsigned short      crc16;
    int                 pack_len = TLV_FIXED_SIZE+1; /*  Only 1 byte data */

    if(!buf || size<TLV_MIN_SIZE || signal > 99 || signal < 0 )
    {
        printf("Invalid input arguments\n");
        return 0;
    }

    /*  Packet head */
    buf[0] = PACK_HEADER;

    /*  Tag */
    buf[1] = TAG_SIGNAL;

    /*  Length, this packet total 6 bytes */
    buf[2] = pack_len;

    /*  Value */
    buf[3] = signal;

    /*  Calc CRC16 checksum value from Packet Head(buf[0])~ Packet Value(buf[3]) */
    crc16 = crc_itu_t(MAGIC_CRC,(unsigned char*) buf, 4);

    /*  Append the 2 Bytes CRC16 checksum value into the last two bytes in packet buffer */
    ushort_to_bytes((unsigned char*)&buf[4], crc16);

    return pack_len;
}




/********************************************
 *  描述：tlv报文解包
 *
 *  参数：
 *      true_tlv用于返回正确的tlv报文，int *ture_size返回正确的tlv报文大小
 *
 *  返回值：
 *      如果tlv报文处理完了返回0 ，如果有半帧的情况返回半帧tlv报文大小
 *
 ********************************************/
int  unpack_tlv(char *buf, int bytes, char *true_tlv,int *true_size)
{
    int                         i ;
    char                        *tlv_ptr = NULL;
    int                         tlv_len = 0 ;
    unsigned short              crc16,val ;


    if(!buf||!true_tlv)
    {
        printf("Invailed input!\n") ;
        return 0 ;
    }

again:
    if(bytes < TLV_MIN_SIZE)
    {
        //printf("tlv packet too short\n") ;
        return bytes ;
    }

    for(i=0; i< bytes; i++)
    {
        if((unsigned char)buf[i]==PACK_HEADER)
        {
            if(bytes -i <= 2)
            {
                printf("tlv_packet is too short so incomplete\n") ;
                memmove(buf, &buf[i], bytes - i) ;
                return bytes -i ;
            }
            tlv_ptr = &buf[i] ;
            tlv_len = tlv_ptr[2] ;

            if(tlv_len < TLV_MIN_SIZE||tlv_len>TLV_MAX_SIZE )
            {
                memmove(buf, &tlv_ptr[2], bytes-i-2) ;
                bytes = bytes -i -2 ;
                goto again;
            }
            if(tlv_len > bytes - i)
            {
                memmove(buf, tlv_ptr, bytes-i) ;
                printf("tlv_packet is incomplete\n") ;
                return bytes - i ;
            }
            crc16 = crc_itu_t(MAGIC_CRC,(unsigned char*)tlv_ptr, tlv_len-2);
            val = bytes_to_ushort((unsigned char*)&tlv_ptr[tlv_len-2],2) ;
            //printf("crc: %d, val: %d\n",crc16, val) ;
            if(val != crc16)
            {
                printf("CRC check error\n") ;
                memmove(buf, &tlv_ptr[2], bytes-i-2) ;
                bytes = bytes -i -2 ;
                goto again ;
            }
            //printf("CRC check true\n") ;
            memmove(&true_tlv[*true_size], tlv_ptr, tlv_len) ;
            *true_size += tlv_len ; 
            memmove(buf,&tlv_ptr[tlv_len], bytes-i-tlv_len) ;
            bytes = bytes -i -tlv_len ;
            goto again ;
        }//if((unsigned char)buf[i] == PACK_HEADER)
    }//for(i=0;i<bytes;i++)
    return 0 ;
}

/**************************************** *
 *  描述：以16进制形式打印tlv报文
 *
 * ******************************************/
void print_tlv(char* type,char *buf, int len) 
{
    int i ;

    if(!buf)
    {
        printf("Invaild input\n") ;
        return ;
    }
    if(type)
    {
        printf("\n%s:\n",type) ;
    }
    for(i=0;i<len;i++)
    {
        if((unsigned char)buf[i]==PACK_HEADER)
        {
            printf("\n") ;
        }
        printf("0x%02x  ",(unsigned char)buf[i]) ;
    }
    printf("\n") ;
}



/*******************************************************
 *  描述：检查登录密码
 *  
 *  返回值:
 *          密码正确返回LOGON_OK，密码错误返回LOGON_ERROR
 *
 *******************************************************/
int tlv_logon(char *buf,int size)
{
    int     i ;
    char    passwd[32] ;
    int     tlv_len ;

    if(!buf||size < TLV_MIN_SIZE)
    {
        printf("Invailed input!\n") ;
        return LOGON_ERROR ;
    }

    memset(passwd, 0, sizeof(passwd)) ;
    for(i=0;i<size;i++)
    {
        if((unsigned char)buf[i] == PACK_HEADER)
        {
            if(buf[i+1] == TAG_LOGON )
            {
                tlv_len = buf[i+2] ;
                snprintf(passwd,tlv_len-4,"%s",&buf[i+3]) ;
                if(!strcmp(passwd,"iot@yun"))
                {
                    return LOGON_OK ;
                }
                else
                {
                    return LOGON_ERROR ;
                }
            }
        }
    }
    return LOGON_ERROR ;
}


/*********************************************
 * 描述：登录答复，三秒内需要发送登录报文
 *
 * 返回值：
 *       成功返回0， 失败返回负数
 *
 * ******************************************/
int logon_rev(int cli_fd, char *buf, int size)
{
    fd_set                  rset ;
    struct timeval          timeout; 
    int                     rv;

    if(!buf)
    {
        printf("Invaild input\n") ;
        return -1 ;
    }
    timeout.tv_sec = 3;
    timeout.tv_usec = 0; 
    FD_ZERO(&rset) ;
    FD_SET(cli_fd, &rset) ;
    rv = select(cli_fd+1, &rset, NULL, NULL, &timeout) ;
    if(rv < 0) //select error.
    {
        printf("select() failed: %s\n", strerror(errno)) ;
        return -2 ;
    }
    if(rv== 0) //select time out.
    {
        printf("Time out,client no send passwd\n") ;
        return -3 ;
    }
    rv = read(cli_fd, buf, size) ;
    if(rv <= 0)//read from client failed 
    {
        printf("Read from buf error or disconnect:%s\n", strerror(errno)) ;
        return -4 ;
    }
    return rv ;
}



/****************************************************
 *  描述：tlv报文解析操作,根据tlv报文PPP拨号或发短信
 *
 *************************************************/
int tlv_gprs(int cli_fd ,char *buf,int size, st_gsmd_ctx *gsmd_ctx)
{
    char            sbuf[128] ;
    int             i ;
    int             sms_len ;
    int             num_len = 0 ;
    int             mess_len = 0 ;
    int             ofset = 0 ;
    int             find = 1 ;


    if(!buf||size<TLV_MIN_SIZE)
    {
        printf("\nUnkown cmd\n") ;
        return 0 ;
    }
    memset(sbuf, 0, sizeof(sbuf)) ;
    for(i=0;i<size;i++) 
    {
        if((unsigned char)buf[i]==PACK_HEADER)
        {
            switch((unsigned char)buf[i+1])
            {
                case TAG_PPP:
                    if((unsigned char)buf[i+3] == ON)
                    {
                        printf("\nStart PPP connect....\n") ;
                        pthread_mutex_lock(&(gsmd_ctx->worker.lock));
                        gsmd_ctx->worker.PPP = ON ;
                        gsmd_ctx->worker.request |= REQ_PPP ;
                        pthread_mutex_unlock(&(gsmd_ctx->worker.lock));
                    }
                    else if((unsigned char)buf[i+3] == OFF)
                    {
                        printf("\nwill close PPP connect..\n") ;
                        pthread_mutex_lock(&(gsmd_ctx->worker.lock));
                        gsmd_ctx->worker.PPP = OFF ;
                        gsmd_ctx->worker.request |= REQ_PPP ;
                        pthread_mutex_unlock(&(gsmd_ctx->worker.lock));

                    }
                    break ;

                case TAG_SMS:
                    sms_len = (unsigned char)buf[i+2] ;
                    sms_len = sms_len -2 ;//减去两位校验和
                    ofset = i+3 ;
                    if((unsigned char)buf[ofset] == 0x1B) //第一个0x1B号码起始位置
                    {
                        while((ofset - i) < sms_len)
                        {
                            ofset ++ ; 
                            if((unsigned char)buf[ofset] == 0x1B) //第二个0x1B号码结束位置，和短信内容起始位置
                            {
                                find = 1 ;
                                break ;
                            }
                        }
                        if(find)
                        {
                            num_len = ofset -i -4 ;//i找到帧头0xFD的地方，类型、长度、还有两个0x1B
                            if(num_len > 16)
                            {
                                printf("Invail telephone number\n") ;
                                return 0 ;
                            }
                            strncpy(gsmd_ctx->worker.tel_num, &buf[i+4], num_len) ;
                            find = 0 ;
                            while((ofset - i) < sms_len)
                            {
                                ofset ++ ; 
                                if((unsigned char)buf[ofset] == 0x1C) //0x1C标志短信内容结束位置
                                {
                                    find = 1 ;
                                    break ;
                                }
                            }
                            if(find)
                            {
                                mess_len = ofset - (num_len+i+4) - 1 ;//号码结束的位置,短信结束的位置
                                if(mess_len > 512)
                                {
                                    printf("Message too long, please send shorter than 512 bytes\n") ;
                                    return 0 ;
                                }
                                memset(gsmd_ctx->worker.mess, 0 , sizeof(gsmd_ctx->worker.mess)) ;
                                strncpy(gsmd_ctx->worker.mess, &buf[num_len+i+5], mess_len) ; //从号码结束的下一位开始
                                printf("send \"%s\" to %s\n", gsmd_ctx->worker.mess, gsmd_ctx->worker.tel_num) ;
                                pthread_mutex_lock(&(gsmd_ctx->worker.lock));
                                gsmd_ctx->worker.request |= REQ_SMS ;
                                pthread_mutex_unlock(&(gsmd_ctx->worker.lock));
                            }
                        }
                    }//if((unsigned char)buf[ofset] == 0x1B)
                    break ;

                default:
                    printf("Unknow tlv\n") ;
                    break ;
            } //switch((unsigned char)buf[i+1])

        }// if((unsigned char)buf[i]==PACK_HEADER)

    } //for(i=0;i<size;i++) 
    return 0 ;
}

